# coding=utf8
__author__ = 'lijian'
import pty
import os
import select
import threading
import struct
import datetime
import manager

from utils import byteUtil
from utils import bcdUtil
import calendar

import setting
from xml.dom import minidom

class Packet(object):
    def __init__(self, ctrl=0x0, data=[], uid=None, cs=0, raw=None):
        self._uid = uid
        self._ctrl = ctrl
        self._data = data
        self._dataLen = len(data)
        self._cs = cs

        self._raw = raw


    def encode(self):
        if not self._raw:
            self._raw = []
            self._raw.append(0x68)
            if not self._uid:
                self._uid = (0, 0, 0, 0, 0, 0)
            self._raw.extend(self._uid)
            self._raw.append(0x68)

            self._raw.append(0x01)
            self._dataLen = len(self._data)+1+2

            self._raw.extend(byteUtil.encodeShort(self._dataLen))

            self._raw.extend([0x04,0x02])
            self._raw.append(self._ctrl)
            self._raw.extend(self._data)

            self.__calcCS__(self._raw)
            self._raw.append(self._cs)
            self._raw.append(0x16)

            return self._raw

    @property
    def uid(self):
        return tuple(self._uid)

    @uid.setter
    def uid(self, value):
        self._uid = value
        self._raw = None

    @property
    def dataLen(self):
        return self._dataLen

    @property
    def dataRegion(self):
        return tuple(self._data)

    @property
    def ctrl(self):
        return self._ctrl

    def pack(self):
        if not self._raw:
            self.encode()
        return struct.pack('B' * len(self._raw), *self._raw)

    def __calcCS__(self, datas):
        cs = 0
        for b in datas:
            cs += b
        cs = cs & 0xff
        self._cs = cs

    def info(self):
        if not self._raw:
            self.encode()

        pack = ""
        i = 0
        for char in self._raw:
            if (i == 8):
                pack += " : "
            if char < 16:
                pack += "0" + hex(char).replace("0x", "") + " ";
            else:
                pack += hex(char).replace("0x", "") + " ";
            i += 1
        return pack.upper()

def enum(**enums):
    return type('Enum', (), enums)

class PacketDecode(object):
    STATE = enum(WAIT=1, UID=2, DEP=3,FIX=4, LEN=5,CTRL=6, DATA=7, CS=8, TAIL=9)

    def __init__(self):
        self.state = PacketDecode.STATE.WAIT

        self.stateRecvLen = 0  # 当前状态已接收的字节数
        self.stateNeedLen = 0  # 当前状态应接收字节数
        self.stateRemainLen = 0  # 当前状态剩余应接收字节
        self.stateRecvBytes = []  # 当前状态已接收的字节

        self.reset()


    def reset(self):
        self.frameRaw = []

        self.uid = []
        self.ctrl = 0
        self.dataLen = 0
        self.dataRegion = []
        self.cs = 0

        self.changeSatet(PacketDecode.STATE.WAIT)


    def changeSatet(self, state):
        result = {
            PacketDecode.STATE.WAIT: 1,
            PacketDecode.STATE.UID: 6,
            PacketDecode.STATE.DEP: 1,
            PacketDecode.STATE.FIX: 1,
            PacketDecode.STATE.LEN: 2,
            PacketDecode.STATE.CTRL: 1,
            PacketDecode.STATE.DATA: self.dataLen-1,
            PacketDecode.STATE.CS: 1,
            PacketDecode.STATE.TAIL: 1,
        }

        self.state = state
        self.stateRecvLen = 0
        self.stateRecvBytes = []
        self.stateRemainLen = result[state]
        self.stateNeedLen = self.stateRemainLen

    def checkStateData(self, data):
        size, bs = byteUtil.getBytes(data[self.offset:], self.stateRemainLen)
        self.stateRecvLen += size
        self.stateRecvBytes.extend(bs)

        self.offset += size

        if self.stateRecvLen == self.stateNeedLen:
            if self.state != PacketDecode.STATE.WAIT:
                self.frameRaw.extend(self.stateRecvBytes)
            return True
        else:
            self.stateRemainLen -= size
            return False

    def checkTimeOut(self):
        return False

    def checkCS(self):
        return True

    def decode(self, data):
        if not data:
            return None

        self.checkTimeOut()

        self.offset = 0
        while len(data[self.offset:]) > 0:
            if not self.checkStateData(data):
                continue

            if self.state == PacketDecode.STATE.WAIT:
                byte = self.stateRecvBytes[0]
                if byte == 0x68:
                    self.frameRaw = [byte]
                    self.changeSatet(PacketDecode.STATE.UID)
                else:
                    self.reset()
            elif self.state == PacketDecode.STATE.UID:
                self.uid = self.stateRecvBytes
                self.changeSatet(PacketDecode.STATE.DEP)
            elif self.state == PacketDecode.STATE.DEP:
                if self.stateRecvBytes[0] == 0x68:
                    self.changeSatet(PacketDecode.STATE.FIX)
                else:
                    self.reset()
            elif self.state == PacketDecode.STATE.FIX:
                fix = self.stateRecvBytes[0]
                self.changeSatet(PacketDecode.STATE.LEN)
            elif self.state == PacketDecode.STATE.LEN:
                self.dataLen = byteUtil.decodeShort(self.stateRecvBytes)
                self.changeSatet(PacketDecode.STATE.CTRL)
            elif self.state==PacketDecode.STATE.CTRL:
                self.ctrl=self.stateRecvBytes[0]
                self.changeSatet(PacketDecode.STATE.DATA)
            elif self.state == PacketDecode.STATE.DATA:
                self.dataRegion = self.stateRecvBytes
                self.changeSatet(PacketDecode.STATE.CS)
            elif self.state == PacketDecode.STATE.CS:
                if self.checkCS():
                    self.cs = self.stateRecvBytes[0]
                    self.changeSatet(PacketDecode.STATE.TAIL)
                else:
                    self.reset()
            elif self.state == PacketDecode.STATE.TAIL:
                if self.stateRecvBytes[0] == 0x16:
                    packet = Packet(self.ctrl, self.dataRegion, self.uid, self.cs, self.frameRaw)
                    self.reset()
                    return packet
                else:
                    self.reset()

        return None


master = ()
_decoder = PacketDecode()

def mkpty():
    global master
    master, slave = pty.openpty()
    slaveName = os.ttyname(slave)
    print("成功创建 RTU 虚拟串口" + slaveName)
    setting.RTU_SERIAL["port"] = slaveName
    return master


def monitorSerial():
    global master
    while True:
        rl, wl, el = select.select([master], [], [], 1)
        for master in rl:
            buff = os.read(master, 256)
            if buff:
                remain = struct.unpack('B' * len(buff), buff)
                packet = _decoder.decode(remain)
                if packet:
                    handlePacket(packet)


def start():
    mkpty()

    loadActionRecord()
    th = threading.Thread(target=monitorSerial)
    th.setDaemon(True)
    th.start()


def handlePacket(packet):
    func = _ctrlDict[packet.ctrl]
    if func:
        response = func(packet)
        if response:
            os.write(master, response.pack())


def syncTime(packet):
    now = datetime.datetime.now()

    data = [now.year - 2000, now.month, now.day, now.hour, now.minute, now.second]
    response = Packet(0x60, data)
    return response


def remoteCtrl(packet):
    response = Packet(0x62, packet.dataRegion)
    return response


def queryDate(packet):
    data = []
    data.append(1)
    data.extend([1] * 26)

    for i in range(81):
        value = 0
        if _analogValue.has_key(i):
            value = _analogValue[i]["value"]

        value = float(value)
        raw = byteUtil.encodeFloat(value)
        data.extend(raw)

    response = Packet(0x61, data)
    return response


def readTimeSheet(packet):
    raw = packet.dataRegion

    month = raw[0]
    day = raw[1]
    size = byteUtil.decodeShort(raw[2:4])

    date = "%d-%d" % (month, day)
    actions = getActions(date, size)

    data = []
    data.append(month)
    data.append(day)
    data.extend(byteUtil.encodeShort(len(actions)))

    for action in actions:
        bcd = bcdUtil.hexTobcd(action["onTime"])
        data.append(bcd[1])
        data.append(bcd[0])

        bcd = bcdUtil.hexTobcd(action["offTime"])
        data.append(bcd[1])
        data.append(bcd[0])

    response = Packet(0x63, data)
    return response


def writeTimeSheet(packet):
    raw = packet.dataRegion

    month = raw[0]
    day = raw[1]
    dayCount = byteUtil.decodeShort(raw[2:4])

    offset = 4
    records = []

    i = 0
    while i < dayCount:
        bcd = bcdUtil.bcdTohex(raw[offset:offset + 2])
        minute = bcd[0]
        hour = bcd[1]
        onTime = [hour, minute]
        offset += 2

        bcd = bcdUtil.bcdTohex(raw[offset:offset + 2])
        minute = bcd[0]
        hour = bcd[1]
        offTime = [hour, minute]
        offset += 2

        record = {
            "date": "%d-%d" % (month, day),
            "onTime": onTime,
            "offTime": offTime
        }

        records.append(record)
        month, day = manager.addDay(month, day)
        i += 1
        if month > 12:
            month = 1
            day = 1

    replaceActions(records)

    data = [1]
    response = Packet(0x64, data)
    return response


_actionList = []


def replaceActions(actions):
    for new in actions:
        old = findActionRecord(new["date"])

        old["onTime"] = new["onTime"]
        old["offTime"] = new["offTime"]

    impl = minidom.getDOMImplementation()
    dom = impl.createDocument(None, 'actions', None)
    root = dom.documentElement

    for record in _actionList:
        action = dom.createElement('action')
        action.setAttribute("date", "%s" % record["date"])
        action.setAttribute("onTime", "%r" % record["onTime"])
        action.setAttribute("offTime", "%r" % record["offTime"])

        root.appendChild(action)

    f = open("rtu/actions.xml", 'w')
    dom.writexml(f, addindent='  ', newl='\n', encoding='utf-8')
    f.close()


def findActionRecord(date):
    for record in _actionList:
        if record["date"] == date:
            return record


def loadActionRecord():
    for month in range(1, 13):
        for day in range(1, manager.monthDays[month - 1] + 1):
            key = "%d-%d" % (month, day)
            action = {"date": key, "onTime": [18, 0], "offTime": [6, 0]}
            _actionList.append(action)

    try:
        doc = minidom.parse("rtu/actions.xml")
        root = doc.documentElement

        eles = root.getElementsByTagName("action")
        for ele in eles:
            date = ele.getAttribute("date").encode("ISO-8859-1")

            onTimeStr = ele.getAttribute("onTime").encode("ISO-8859-1")
            onTime = eval(onTimeStr)

            offTimeStr = ele.getAttribute("offTime").encode("ISO-8859-1")
            offTime = eval(offTimeStr)

            action = findActionRecord(date)
            if action:
                action["onTime"] = onTime
                action["offTime"] = offTime

    except Exception as e:
        print e


def getActions(date, size):
    global _actionList
    startIndex = -1
    for i in range(len(_actionList)):
        action = _actionList[i]
        if action["date"] == date:
            startIndex = i
            break

    if startIndex != -1:
        actions = _actionList[startIndex:size]
        return actions
    else:
        return None


_ctrlDict = {
    0x60: syncTime,
    0x62: remoteCtrl,
    0x61: queryDate,
    0x63: readTimeSheet,
    0x64: writeTimeSheet
}

UNKNOW = 0x00  # 未知
V = 0x01  # 电压
A = 0x02  # 电流
AP = 0x03  # 有功功率
RP = 0x04  # 无功功率
PF = 0x05  # 功率因数
PL = 0x06  # 频率
XJ = 0x0  # 相角
ZD = 0x08  # 照度

_analogValue = {
    0: {"type": V, "value": 220},
    1: {"type": A, "value": 1.13},
    2: {"type": V, "value": 210},
    3: {"type": A, "value": 2.43},
    4: {"type": V, "value": 250},
    5: {"type": A, "value": 0.89},
    6: {"type": AP, "value": 2000},
    7: {"type": RP, "value": 2500},
    8: {"type": PF, "value": 0.75},

    9: {"type": V, "value": 220},
    10: {"type": A, "value": 1.13},
    11: {"type": V, "value": 210},
    12: {"type": A, "value": 2.43},
    13: {"type": V, "value": 250},
    14: {"type": A, "value": 0.89},
    15: {"type": AP, "value": 2000},
    16: {"type": RP, "value": 2500},
    17: {"type": PF, "value": 0.75},

    18: {"type": V, "value": 221},
    19: {"type": V, "value": 222},
    20: {"type": V, "value": 223},

    21: {"type": A, "value": 1.1},
    22: {"type": A, "value": 1.2},
    23: {"type": A, "value": 1.3},
    24: {"type": AP, "value": 1000},
    25: {"type": RP, "value": 1100},
    26: {"type": PF, "value": 0.86},

    27: {"type": A, "value": 1.1},
    28: {"type": A, "value": 1.2},
    29: {"type": A, "value": 1.3},
    30: {"type": AP, "value": 1000},
    31: {"type": RP, "value": 1100},
    32: {"type": PF, "value": 0.86},

    33: {"type": A, "value": 1.1},
    34: {"type": A, "value": 1.2},
    35: {"type": A, "value": 1.3},
    36: {"type": AP, "value": 1000},
    37: {"type": RP, "value": 1100},
    38: {"type": PF, "value": 0.86},

    39: {"type": A, "value": 1.1},
    40: {"type": A, "value": 1.2},
    41: {"type": A, "value": 1.3},
    42: {"type": AP, "value": 1000},
    43: {"type": RP, "value": 1100},
    44: {"type": PF, "value": 0.86},

    45: {"type": A, "value": 1.1},
    46: {"type": A, "value": 1.2},
    47: {"type": A, "value": 1.3},
    48: {"type": AP, "value": 1000},
    49: {"type": RP, "value": 1100},
    50: {"type": PF, "value": 0.86},

    51: {"type": A, "value": 1.1},
    52: {"type": A, "value": 1.2},
    53: {"type": A, "value": 1.3},
    54: {"type": AP, "value": 1000},
    55: {"type": RP, "value": 1100},
    56: {"type": PF, "value": 0.86}
}